Skip to content

feat(api): add credit purchase idempotency key - #4592

Merged
tothandras merged 1 commit into
mainfrom
feat/credit-grant-idempotency-key
Jun 29, 2026
Merged

feat(api): add credit purchase idempotency key#4592
tothandras merged 1 commit into
mainfrom
feat/credit-grant-idempotency-key

Conversation

@tothandras

@tothandras tothandras commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added an optional idempotency key when creating credit grants (request/response models and API surface).
    • Reusing the same key now returns a conflict instead of creating duplicates, making retries safer.
  • Bug Fixes

    • Credit-purchase records now persist the idempotency key and enforce uniqueness per namespace for active records.
    • Soft-deleted records no longer block reuse of the same key.
  • Tests

    • Added end-to-end coverage for idempotency behavior, duplicate handling, cross-customer conflicts, and invalid key length.
    • Removed a credit-grant validation test for missing tax code behavior.

@tothandras
tothandras requested a review from a team as a code owner June 28, 2026 21:48
@tothandras tothandras added the release-note/feature Release note: Exciting New Features label Jun 28, 2026
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 369a713d-7386-48f9-9167-e176aa668a04

📥 Commits

Reviewing files that changed from the base of the PR and between d68eff6 and 2d75c96.

⛔ Files ignored due to path filters (9)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
  • openmeter/ent/db/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (15)
  • api/spec/packages/aip-client-javascript/src/models/schemas.ts
  • api/spec/packages/aip-client-javascript/src/models/types.ts
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • e2e/customer_credits_v3_test.go
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/ent/schema/chargescreditpurchase.go
  • test/credits/creditgrant_test.go
  • tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.down.sql
  • tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.up.sql
💤 Files with no reviewable changes (1)
  • test/credits/creditgrant_test.go
🚧 Files skipped from review as they are similar to previous changes (14)
  • openmeter/billing/creditgrant/service/service.go
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.up.sql
  • openmeter/ent/schema/chargescreditpurchase.go
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • api/spec/packages/aip-client-javascript/src/models/schemas.ts
  • tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.down.sql
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • api/spec/packages/aip-client-javascript/src/models/types.ts
  • e2e/customer_credits_v3_test.go

📝 Walkthrough

Walkthrough

Adds an optional idempotency key to credit grant creation, stores it with a partial unique index on active records, propagates it through API and service layers, and adds E2E coverage for conflict, duplicate, namespace, and length behavior.

Changes

Credit Grant Idempotency Key

Layer / File(s) Summary
DB migration and Ent schema
tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.*.sql, openmeter/ent/schema/chargescreditpurchase.go
Adds a nullable key column and a unique partial index on (namespace, key) for non-deleted rows, with matching Ent field and index declarations.
Domain types and internal service contracts
openmeter/billing/charges/creditpurchase/charge.go, openmeter/billing/creditgrant/service.go, openmeter/billing/creditgrant/service/service.go
Intent and CreateInput gain optional Key fields, and toIntent copies CreateInput.Key into the charge intent.
Adapter: persist and read back key
openmeter/billing/charges/creditpurchase/adapter/charge.go, openmeter/billing/charges/creditpurchase/adapter/mapper.go
The charge create path writes the key to the DB entity, and the mapper reads it back into the domain model.
API handler conversion and generated Go types
api/v3/api.gen.go, api/v3/handlers/customers/credits/convert.go
The generated request type gains Key, the handler copies it into billing input, and the embedded Swagger spec is updated.
API spec and JS client types/schemas
api/spec/packages/aip/src/customers/credits/grant.tsp, api/spec/packages/aip-client-javascript/src/models/schemas.ts, api/spec/packages/aip-client-javascript/src/models/types.ts
The TypeSpec model, generated Zod schemas, and TypeScript types all add the optional key field with matching docs.
E2E tests and removed stale test
e2e/customer_credits_v3_test.go, test/credits/creditgrant_test.go
Adds E2E coverage for key reuse, duplicates without a key, cross-customer collisions, and over-length rejection, and removes the obsolete missing-tax-code validation test.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • openmeterio/openmeter#4066: Touches the same credit-purchase charge creation and mapping path, with related intent-field propagation changes.
  • openmeterio/openmeter#4590: Overlaps on test/credits/creditgrant_test.go and the missing-tax-code validation behavior that was removed here.

Suggested labels

area/billing

Suggested reviewers

  • turip
  • borbelyr-kong
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches a real part of the change by adding an idempotency key for credit purchase flows, though it doesn’t mention the broader credit grant API updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/credit-grant-idempotency-key

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds idempotency support for credit grant creation. The main changes are:

  • Adds a create request key field to the API and SDK models.
  • Persists the key on credit-purchase charge records.
  • Adds a partial unique index for active records with the same namespace and key.
  • Adds tests for duplicate keys, omitted keys, cross-customer conflicts, and invalid key length.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
api/spec/packages/aip/src/customers/credits/grant.tsp Adds the create-only idempotency key to the credit grant API model.
api/v3/handlers/customers/credits/convert.go Maps the create request key into the credit grant creation input.
openmeter/billing/charges/creditpurchase/adapter/charge.go Stores the optional key when creating a credit-purchase charge.
openmeter/ent/schema/chargescreditpurchase.go Adds the persisted key field and active-record uniqueness rule.
tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.up.sql Adds the database column and partial unique index for idempotency keys.
e2e/customer_credits_v3_test.go Adds end-to-end coverage for the new idempotency behavior.

Reviews (5): Last reviewed commit: "fix(api): add idempotency key to create ..." | Re-trigger Greptile

Comment thread api/spec/packages/aip/src/customers/credits/grant.tsp Outdated
Comment thread openmeter/billing/charges/creditpurchase/adapter/charge.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/spec/packages/aip/src/customers/credits/grant.tsp`:
- Around line 196-205: The shared CreditGrant model is exposing the create-only
idempotency_key in read-side generated contracts, so move this field off the
reusable CreditGrant shape and into a request-only create model or equivalent
create input used by the grant operation. Update the TypeSpec around the grant
creation model so the shared response/read model stays free of idempotency_key
while the create request still accepts it, keeping the generated schemas/types
in sync with the intended contract.

In `@openmeter/billing/creditgrant/service.go`:
- Around line 60-68: `CreateInput.Validate()` needs to enforce the same
`IdempotencyKey` length limit as the API/DB contract. Update the validation
logic in `CreateInput.Validate` to add an error when `IdempotencyKey` is present
and longer than 255 characters, using the existing `var errs []error` pattern
and returning `models.NewNillableGenericValidationError(errors.Join(errs...))`
rather than failing fast. Make sure the check is applied alongside the other
field validations in `CreateInput`.

In `@openmeter/ent/schema/chargescreditpurchase.go`:
- Around line 67-70: The idempotency key field in the ent schema is currently
unbounded, so update the `field.String("idempotency_key")` definition in
`chargescreditpurchase.go` to enforce the 255-character limit by adding
`MaxLen(255)` alongside the existing optional/nillable/immutable settings. Keep
the change in the `ent/schema` source of truth so the generated column uses the
same DB contract as the public API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 126094f1-79b4-4864-b558-439f795542f6

📥 Commits

Reviewing files that changed from the base of the PR and between 33cf406 and 7e7e1fc.

⛔ Files ignored due to path filters (9)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
  • openmeter/ent/db/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (16)
  • api/spec/packages/aip-client-javascript/src/models/schemas.ts
  • api/spec/packages/aip-client-javascript/src/models/types.ts
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • e2e/customer_credits_v3_test.go
  • e2e/v3helpers_test.go
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/ent/schema/chargescreditpurchase.go
  • test/credits/creditgrant_test.go
  • tools/migrate/migrations/20260628214302_credit-purchase-idempotency-key.down.sql
  • tools/migrate/migrations/20260628214302_credit-purchase-idempotency-key.up.sql
💤 Files with no reviewable changes (2)
  • test/credits/creditgrant_test.go
  • e2e/v3helpers_test.go

Comment thread api/spec/packages/aip/src/customers/credits/grant.tsp Outdated
Comment on lines +60 to +68
Currency currencyx.Code
Amount alpacadecimal.Decimal
Priority *int16
FundingMethod FundingMethod
Purchase *PurchaseTerms
TaxConfig *productcatalog.TaxConfig
Filters *GrantFilters
ExpiresAfter *datetime.ISODuration
IdempotencyKey *string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate IdempotencyKey in CreateInput.Validate() too.

The new field enters the domain here, but Validate() still accepts arbitrary-length values. That lets non-HTTP callers bypass the 255-character limit and drift from the API/DB contract. Please append a validation error for keys longer than 255 characters.

Suggested fix
+import "unicode/utf8"
+
 func (i CreateInput) Validate() error {
 	var errs []error
@@
 	if i.Filters != nil {
 		if err := creditpurchase.FeatureFilters(i.Filters.Features).Validate(); err != nil {
 			errs = append(errs, fmt.Errorf("filters.features: %w", err))
 		}
 	}
+
+	if i.IdempotencyKey != nil && utf8.RuneCountInString(*i.IdempotencyKey) > 255 {
+		errs = append(errs, errors.New("idempotency_key must be at most 255 characters"))
+	}
 
 	return models.NewNillableGenericValidationError(errors.Join(errs...))
 }

As per coding guidelines, For Go Validate() error methods, collect all validation issues into var errs []error and return models.NewNillableGenericValidationError(errors.Join(errs...)) instead of failing fast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/billing/creditgrant/service.go` around lines 60 - 68,
`CreateInput.Validate()` needs to enforce the same `IdempotencyKey` length limit
as the API/DB contract. Update the validation logic in `CreateInput.Validate` to
add an error when `IdempotencyKey` is present and longer than 255 characters,
using the existing `var errs []error` pattern and returning
`models.NewNillableGenericValidationError(errors.Join(errs...))` rather than
failing fast. Make sure the check is applied alongside the other field
validations in `CreateInput`.

Source: Coding guidelines

Comment thread openmeter/ent/schema/chargescreditpurchase.go Outdated
@tothandras
tothandras force-pushed the feat/credit-grant-idempotency-key branch from 7e7e1fc to d66eca1 Compare June 29, 2026 11:18
Comment on lines +3649 to +3655
/**
* Idempotency key for the credit grant creation request.
*
* When provided, reusing the same key returns an HTTP 409 Conflict
* instead of creating a duplicate grant, which makes create requests safe to retry.
*/
key?: string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Response Type Leaks Key
The server treats key as create-only, but the generated TypeScript response model still exposes it on CreditGrant. The OpenAPI response schema and Go response conversion do not serialize this field, so TS callers of create/get/list can read CreditGrant.key from the SDK type and always receive undefined at runtime. Please keep the field on CreateCreditGrantRequest only, or update the response contract and serializer to return it consistently.

Context Used: api/spec/AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: api/spec/packages/aip-client-javascript/src/models/types.ts
Line: 3649-3655

Comment:
**Response Type Leaks Key**
The server treats `key` as create-only, but the generated TypeScript response model still exposes it on `CreditGrant`. The OpenAPI response schema and Go response conversion do not serialize this field, so TS callers of create/get/list can read `CreditGrant.key` from the SDK type and always receive `undefined` at runtime. Please keep the field on `CreateCreditGrantRequest` only, or update the response contract and serializer to return it consistently.

**Context Used:** api/spec/AGENTS.md ([source](https://app.greptile.com/openmeter/github/openmeterio/openmeter/-/custom-context?memory=28ba6068-00f9-4629-9b78-8e49cc802858))

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/spec/packages/aip/src/customers/credits/grant.tsp`:
- Around line 196-200: The documentation for the credit grant idempotency key in
grant.tsp currently implies any key reuse always returns 409, which is broader
than the actual behavior. Update the comment near the credit grant request
definition to state the idempotency scope accurately: duplicate reuse should
conflict only within the same customer/context, while cross-customer reuse is
allowed. Keep the wording aligned with the request/response contract described
by the grant creation model so the docs match the implementation.

In `@openmeter/ent/schema/chargescreditpurchase.go`:
- Around line 123-130: The uniqueness constraint in the charges credit purchase
schema is too broad because `index.Fields("namespace", "key")` makes `key`
unique across an entire namespace instead of per customer. Update the unique
index in `chargescreditpurchase.go` to include `customer_id` (or the intended
ownership boundary) alongside `namespace` and `key`, keeping the partial
`deleted_at IS NULL` condition. Then regenerate the migration so the database
schema matches the updated uniqueness scope.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ce6d975b-7b1f-41b3-9ceb-4849ac072384

📥 Commits

Reviewing files that changed from the base of the PR and between 7e7e1fc and d66eca1.

⛔ Files ignored due to path filters (9)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
  • openmeter/ent/db/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (15)
  • api/spec/packages/aip-client-javascript/src/models/schemas.ts
  • api/spec/packages/aip-client-javascript/src/models/types.ts
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • e2e/customer_credits_v3_test.go
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/ent/schema/chargescreditpurchase.go
  • test/credits/creditgrant_test.go
  • tools/migrate/migrations/20260629111024_credit-purchase-idempotency-key.down.sql
  • tools/migrate/migrations/20260629111024_credit-purchase-idempotency-key.up.sql
💤 Files with no reviewable changes (1)
  • test/credits/creditgrant_test.go
✅ Files skipped from review due to trivial changes (1)
  • api/v3/api.gen.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/billing/charges/creditpurchase/adapter/charge.go

Comment on lines +196 to +200
/**
* Idempotency key for the credit grant creation request.
*
* When provided, reusing the same key returns an HTTP 409 Conflict instead of
* creating a duplicate grant, which makes create requests safe to retry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clarify the idempotency scope in the docs.

The wording reads like any key reuse returns 409, but the supplied stack context says cross-customer reuse is allowed. Tightening this to the actual scope would keep the contract honest.

✏️ Suggested wording
- * When provided, reusing the same key returns an HTTP 409 Conflict instead of
- * creating a duplicate grant, which makes create requests safe to retry.
+ * When provided, reusing the same key for the same customer returns an HTTP 409
+ * Conflict instead of creating a duplicate grant, which makes create requests
+ * safe to retry.

As per path instructions, "The declared API should be accurate, in parity with the actual implementation, and easy to understand."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Idempotency key for the credit grant creation request.
*
* When provided, reusing the same key returns an HTTP 409 Conflict instead of
* creating a duplicate grant, which makes create requests safe to retry.
/**
* Idempotency key for the credit grant creation request.
*
* When provided, reusing the same key for the same customer returns an HTTP 409
* Conflict instead of creating a duplicate grant, which makes create requests
* safe to retry.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/spec/packages/aip/src/customers/credits/grant.tsp` around lines 196 -
200, The documentation for the credit grant idempotency key in grant.tsp
currently implies any key reuse always returns 409, which is broader than the
actual behavior. Update the comment near the credit grant request definition to
state the idempotency scope accurately: duplicate reuse should conflict only
within the same customer/context, while cross-customer reuse is allowed. Keep
the wording aligned with the request/response contract described by the grant
creation model so the docs match the implementation.

Source: Path instructions

Comment thread openmeter/ent/schema/chargescreditpurchase.go
@tothandras
tothandras force-pushed the feat/credit-grant-idempotency-key branch 2 times, most recently from aacb385 to d68eff6 Compare June 29, 2026 11:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.up.sql`:
- Line 5: The unique index on charge_credit_purchases is scoped too broadly
because it only covers namespace and key, which blocks the intended
cross-customer reuse behavior. Update the migration’s CREATE UNIQUE INDEX for
charge_credit_purchases to include the customer discriminator used by this
table, alongside namespace and key, while preserving the deleted_at filter and
non-null key condition. Adjust the index definition so the uniqueness constraint
matches the API’s customer-level idempotency scope.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 739d8768-3f84-4954-9bba-f458d985f6b1

📥 Commits

Reviewing files that changed from the base of the PR and between aacb385 and d68eff6.

⛔ Files ignored due to path filters (9)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
  • openmeter/ent/db/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (15)
  • api/spec/packages/aip-client-javascript/src/models/schemas.ts
  • api/spec/packages/aip-client-javascript/src/models/types.ts
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • e2e/customer_credits_v3_test.go
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/ent/schema/chargescreditpurchase.go
  • test/credits/creditgrant_test.go
  • tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.down.sql
  • tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.up.sql
💤 Files with no reviewable changes (1)
  • test/credits/creditgrant_test.go
✅ Files skipped from review due to trivial changes (2)
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/billing/charges/creditpurchase/charge.go
🚧 Files skipped from review as they are similar to previous changes (10)
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • api/v3/handlers/customers/credits/convert.go
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • api/spec/packages/aip-client-javascript/src/models/schemas.ts
  • api/v3/api.gen.go
  • openmeter/billing/creditgrant/service.go
  • e2e/customer_credits_v3_test.go
  • openmeter/ent/schema/chargescreditpurchase.go
  • api/spec/packages/aip-client-javascript/src/models/types.ts

ALTER TABLE "charge_credit_purchases" ADD COLUMN "key" character varying NULL;
-- create index "chargecreditpurchase_namespace_key" to table: "charge_credit_purchases"
-- atlas:nolint MF101
CREATE UNIQUE INDEX "chargecreditpurchase_namespace_key" ON "charge_credit_purchases" ("namespace", "key") WHERE ((key IS NOT NULL) AND (deleted_at IS NULL));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Scope the unique index to the customer, not just the namespace.

Right now this makes key unique across every credit purchase in a namespace. That would also reject the “cross-customer reuse” case called out in the stack context, so the DB constraint looks broader than the intended API behavior. Please include the customer discriminator used by charge_credit_purchases in this index as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tools/migrate/migrations/20260629113257_credit-purchase-idempotency-key.up.sql`
at line 5, The unique index on charge_credit_purchases is scoped too broadly
because it only covers namespace and key, which blocks the intended
cross-customer reuse behavior. Update the migration’s CREATE UNIQUE INDEX for
charge_credit_purchases to include the customer discriminator used by this
table, alongside namespace and key, while preserving the deleted_at filter and
non-null key condition. Adjust the index definition so the uniqueness constraint
matches the API’s customer-level idempotency scope.

@tothandras
tothandras force-pushed the feat/credit-grant-idempotency-key branch from d68eff6 to 2d75c96 Compare June 29, 2026 12:24
@tothandras
tothandras enabled auto-merge (squash) June 29, 2026 12:47
@tothandras
tothandras disabled auto-merge June 29, 2026 12:56
@tothandras
tothandras merged commit fcd2968 into main Jun 29, 2026
29 checks passed
@tothandras
tothandras deleted the feat/credit-grant-idempotency-key branch June 29, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants